#include "ESVideo.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdarg.h>

#ifdef __cplusplus
extern "C"{
#endif

#include "x264.h"

#ifdef __cplusplus
}
#endif

typedef struct strH264ENC
{
    x264_param_t	param;
    x264_picture_t	pic;
    x264_t*			enc;
}H264ENC;

void* H264enc_open(int nWidth,int nHeight,int nFrmRate,int nQuant,int nBitrate,int nQMin,int nQMax, int thread_num)
{
    H264ENC * pH264Enc=new H264ENC;

    //int deblock_enable = 1;
    //int thread_num = 1; //multiple threads enable according number of cpu
    //int quality = -6;

    x264_param_default( &pH264Enc->param );

    pH264Enc->param.i_threads= thread_num;
    pH264Enc->param.i_width=nWidth;
    pH264Enc->param.i_height=nHeight;
    pH264Enc->param.i_fps_num = nFrmRate;
	pH264Enc->param.i_fps_den = 1;
    pH264Enc->param.rc.i_qp_constant = nQuant;
    pH264Enc->param.rc.f_rf_constant = nQuant;
    pH264Enc->param.rc.i_qp_min = nQMin;
    pH264Enc->param.rc.i_qp_max = nQMax;

    if (nBitrate) //kbps
    {
        pH264Enc->param.rc.i_rc_method = X264_RC_ABR;
        pH264Enc->param.rc.i_bitrate = nBitrate;
        // small f_rate_tolerance can increase RC precision
        // 1.0 means try to CBR
        pH264Enc->param.rc.f_rate_tolerance = (float)0.1;

        // b_vfr_input default value is 1. then Rate Control module uses PTS to calculate frame distance.
        // for the setting as above, we only set fps and bitrate, and if we don't update output frame PTS value (pH264Enc->pic.i_pts++;).
        // So Rate Control module calculates wrong frame distance, output x264 file is very small and the performance is low.
        // set b_vfr_input = 0, RC module using fps to calculate.
		pH264Enc->param.b_vfr_input = 0;
    }
    else if(nQuant > 0)
    {
        pH264Enc->param.rc.i_rc_method = X264_RC_CQP;
    }
    else
    {
        pH264Enc->param.rc.i_rc_method = X264_RC_CRF;
		pH264Enc->param.rc.f_rf_constant = 23;
    }

    pH264Enc->param.i_keyint_max = 4*nFrmRate;
    pH264Enc->param.i_keyint_min = nFrmRate;

    // increase Speed, drop PSNR
    pH264Enc->param.i_frame_reference = 1;
    // little drop PSNR in high bit rate. i_frame_reference = 1, b_mixed_references must be 0
    //pH264Enc->param.analyse.b_mixed_references = 0;

    // increase Speed, little drop Chroma PSNR in high bitrate
    pH264Enc->param.analyse.b_chroma_me = 0;
    pH264Enc->param.analyse.b_transform_8x8 = 0;
    // increase Speed, decrease RC precision
    pH264Enc->param.analyse.i_trellis = 1;

    // increase Speed
    pH264Enc->param.analyse.i_weighted_pred = X264_WEIGHTP_NONE;

    // increase PSNR
    //pH264Enc->param.analyse.b_psy = 0;
    pH264Enc->param.rc.i_aq_mode = X264_AQ_NONE;

    // little increase Speed, drop PSNR in low bitrate, little increase in high bit rate
    //pH264Enc->param.analyse.b_dct_decimate = 1;

    // Default setting is best
//#define X264_ANALYSE_I4x4       0x0001  /* Analyze i4x4 */
//#define X264_ANALYSE_PSUB16x16  0x0010  /* Analyze p16x8, p8x16 and p8x8 */
//#define X264_ANALYSE_PSUB8x8    0x0020  /* Analyze p8x4, p4x8, p4x4 */

    //pH264Enc->param.analyse.intra = X264_ANALYSE_I4x4;
    //pH264Enc->param.analyse.inter = X264_ANALYSE_I4x4 | X264_ANALYSE_PSUB16x16 | X264_ANALYSE_PSUB8x8;

// IO
    // b_cabac=0, Speed increase about 9%, PSNR drop about 0.3dB, speed better for decoder
    pH264Enc->param.b_cabac = 1;

    // X264_ME_DIA X264_ME_HEX
    pH264Enc->param.analyse.i_me_method = X264_ME_DIA;
    pH264Enc->param.analyse.i_subpel_refine = 5;

    int zerolatency = 1;
    if(zerolatency)
    {
        pH264Enc->param.rc.i_lookahead = 0;
        pH264Enc->param.i_sync_lookahead = 0;
        pH264Enc->param.i_bframe = 0;
        pH264Enc->param.b_sliced_threads = 1;
        pH264Enc->param.b_vfr_input = 0;
        pH264Enc->param.rc.b_mb_tree = 0;
    }
// *************************************************************

    if( ( pH264Enc->enc = x264_encoder_open( &pH264Enc->param ) ) == NULL )
    {
        delete pH264Enc;
        pH264Enc=NULL;
        return NULL;
    }

    x264_encoder_parameters( pH264Enc->enc, &pH264Enc->param );//
    x264_picture_init( &pH264Enc->pic );

    pH264Enc->pic.img.i_stride[0]=pH264Enc->param.i_width;
    pH264Enc->pic.img.i_stride[1]=pH264Enc->param.i_width>>1;
    pH264Enc->pic.img.i_stride[2]=pH264Enc->param.i_width>>1;

    pH264Enc->pic.img.i_csp = X264_CSP_I420;
    pH264Enc->pic.img.i_plane = 3;
    pH264Enc->pic.i_qpplus1 = 0;

    return pH264Enc;
}

void H264enc_close(void* pHandle)
{
    H264ENC * pH264Enc=(H264ENC *)pHandle;

    if (pH264Enc!=NULL)
    {
        if (pH264Enc->enc!=NULL)
        {
            x264_encoder_close( pH264Enc->enc );
            pH264Enc->enc=NULL;
        }

        delete pH264Enc;
        pH264Enc=NULL;
    }
}

int H264enc_encode(void* pHandle, unsigned char *pYV12[],unsigned char* pStreamBuf,int nBufLen,int* pbKeyFrame)
{
    H264ENC * pH264Enc=(H264ENC *)pHandle;

    x264_nal_t *nal;
    int nnal =0, i;
    x264_picture_t pic_out;
    //static int frame_num = 0;
    int i_frame_size;

    for(i = 0; i < 3; i++)
    {
        pH264Enc->pic.img.plane[i] = pYV12[i];
    }

    if (*pbKeyFrame)
    {
        pH264Enc->pic.i_type = X264_TYPE_IDR;
    }
    else
    {
        pH264Enc->pic.i_type = X264_TYPE_AUTO;
    }

    i_frame_size = x264_encoder_encode(pH264Enc->enc, &nal, &nnal, &pH264Enc->pic,&pic_out);
    if (pH264Enc->param.b_vfr_input)
        pH264Enc->pic.i_pts++;
   //printf("frame_num: %3d, i_frame_size: %d\n", frame_num, i_frame_size);

    if(i_frame_size<=0)
    {
        //printf("frame_num: %3d, i_frame_size: %d\n", frame_num++, i_frame_size);
        return i_frame_size;
    }

    //{{ get real encoding qp and frame type(IDR, I, P...), frame enc QP return for cbr frame skip
    {
        pic_out.i_qpplus1 = pic_out.i_qpplus1 & 0xFF;
        *pbKeyFrame = pic_out.i_type | pic_out.i_qpplus1<<8;
        if (X264_TYPE_IDR == pic_out.i_type)
                *pbKeyFrame  =  1;
        else
                *pbKeyFrame  =  0;
    }
    //}}

    //TODO:
    if(nBufLen < i_frame_size)
    {
        return -1;
    }
    nBufLen = i_frame_size;
    if(nBufLen <= 0)
        return -1;
    memcpy((uint8_t*)pStreamBuf, nal[0].p_payload, nBufLen);

    //frame_num++;

    return nBufLen;
}


void *H264enc_open_screen(int nWidth, int nHeight, int nSlice)
{
    int nFrmRate = 30;
    int nQMin = 12;
    int nQMax = 48;
    int nQuant = 28;
    int nBitrate;

    if(nWidth == 1080)
    {
        nQuant +=2;
        nBitrate = (int)(2048/nSlice);
    }
    else if(nWidth == 480)
    {
        nQuant -=2;
        nBitrate = (int)(384/nSlice);
    }
    else // default 720p
    {
        nBitrate = (int)(1024/nSlice);
    }
    return H264enc_open(nWidth, nHeight, nFrmRate, nQuant, nBitrate, nQMin, nQMax, 0);
}

